package cn.stimd.spring.jdbc.datasource;

import cn.stimd.spring.transaction.TransactionDefinition;
import cn.stimd.spring.transaction.TransactionException;
import cn.stimd.spring.transaction.support.AbstractPlatformTransactionManager;
import cn.stimd.spring.transaction.support.DefaultTransactionStatus;
import cn.stimd.spring.transaction.support.TransactionSynchronizationManager;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager {
    private DataSource dataSource;

    public DataSourceTransactionManager(DataSource dataSource) {
        this.dataSource = dataSource;
    }


    //获取实际的事务对象
    @Override
    protected Object doGetTransaction() {
        DataSourceTransactionObject txObject = new DataSourceTransactionObject();
        //尝试获取线程绑定的Connection，第一次为null
        ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
        txObject.setConnectionHolder(holder, false);
        return txObject;
    }


    //检查是否存在事务
    @Override
    protected boolean isExistingTransaction(Object transaction) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        return txObject.getConnectionHolder() != null && txObject.getConnectionHolder().isTransactionActive();
    }


    //开启事务，主要是获取数据库连接，并绑定到事务对象上
    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        Connection con = null;
        try {
            //1. 获取数据库连接，并绑定到事务对象上
            if(txObject.getConnectionHolder() == null){
                Connection newCon = this.dataSource.getConnection();
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }
            con = txObject.getConnectionHolder().getConnection();

            //2. 准备工作
            //设置是否只读与隔离级别
            //Notice: 每个数据库连接都有默认的隔离级别，如果在一次事务中修改了隔离级别，需要把原先的隔离级别属性存储到事务对象中
            //等到事务结束时，再重置数据库连接的隔离级别
            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);

            //关闭自动提交
            if(con.getAutoCommit()){
                con.setAutoCommit(false);
            }

            //设置事务开启标记，判定是否事务存在的依据
            txObject.getConnectionHolder().setTransactionActive(true);

            //设置超时
            if(definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT){
                txObject.getConnectionHolder().setTimeoutInSeconds(definition.getTimeout());
            }

            //3. 将Connection绑定到线程上
            if (txObject.isNewConnectionHolder()) {
                TransactionSynchronizationManager.bindResource(this.dataSource, txObject.getConnectionHolder());
            }
        }catch (Throwable ex){
            if (txObject.isNewConnectionHolder()) {
                DataSourceUtils.releaseConnection(con, this.dataSource);
                txObject.setConnectionHolder(null, false);
            }
            throw new TransactionException("Could not open JDBC Connection for transaction", ex);
        }
    }


    //挂起事务，将数据库连接与事务对象、当前线程解绑
    @Override
    protected Object doSuspend(Object transaction) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        txObject.setConnectionHolder(null);
        return TransactionSynchronizationManager.unbindResource(this.dataSource);
    }

    //恢复事务，将数据库连接与当前线程绑定（但没有绑定到事务对象上）
    @Override
    protected void doResume(Object transaction, Object suspendedResources) {
        TransactionSynchronizationManager.bindResource(this.dataSource, suspendedResources);
    }


    //提交事务，实际由数据库连接完成
    @Override
    protected void doCommit(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        try {
            con.commit();
        } catch (SQLException e) {
            throw new TransactionException("提交事务失败");
        }
    }

    //回滚事务，实际由数据库连接完成
    @Override
    protected void doRollback(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        try {
            con.rollback();
        } catch (SQLException e) {
            throw new TransactionException("回滚事务失败");
        }
    }


    //执行完毕后的清理工作
    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        //解除线程绑定（只有最外层事务方法可以解绑）
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.unbindResource(this.dataSource);
        }

        //重置Connection的隔离级别和只读属性
        Connection con = txObject.getConnectionHolder().getConnection();
        DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());

        //释放数据库连接
        if (txObject.isNewConnectionHolder()) {
            DataSourceUtils.releaseConnection(con, this.dataSource);
        }

        //重置ConnectionHolder
        txObject.getConnectionHolder().clear();
    }


    @Override
    protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        txObject.setRollbackOnly();
    }


    //数据源事务对象
    private static class DataSourceTransactionObject extends JdbcTransactionObjectSupport {
        private boolean newConnectionHolder;

        public void setConnectionHolder(ConnectionHolder connectionHolder, boolean newConnectionHolder) {
            super.setConnectionHolder(connectionHolder);
            this.newConnectionHolder = newConnectionHolder;
        }

        public boolean isNewConnectionHolder() {
            return this.newConnectionHolder;
        }

        public void setRollbackOnly() {
            getConnectionHolder().setRollbackOnly();
        }

        @Override
        public boolean isRollbackOnly() {
            return getConnectionHolder().isRollbackOnly();
        }
    }
}
